<?php

/**
 * This is where the main functionality of the SudokuSover will be done.
 * 
 */
class Puzzle {
	
	private $cells 		= array();
	private $sections 	= array();
	const PUZZLE_3x3	= '3x3configuration.json';
	const TOP_SECTIONS_MULITIPLIER = 1;
	const MIDDLE_SECTIONS_MULITIPLIER = 2;
	const BOTTOM_SECTIONS_MULITIPLIER = 5;
	private $grid = array();
	
	public function __construct()
	{
		
	}
	
//SetUP	
	
	public function initializePuzzle()
	{
		$this->grid = $this->setupCells();
		$this->sections = $this->loadSections();
	}
	
	public function setupCells()
	{
		$cell_array = array();
		for($count = 0; $count < Game::NUMBER_OF_CELLS; $count++)
		{
			$cell = new Cell($count);
			$cell_array[] = $cell;
		}
		return $cell_array;
	}
	
	public static function loadSections()
	{
    	$cells = file_get_contents(Puzzle::PUZZLE_3x3);
		
		$json_array = json_decode($cells, true);
		$cell_array = $json_array['sections'];

		$board_cells = array();
		foreach ($cell_array as $cell) {		
			$section = new Section($cell);
			$board_cells[] = $section;
		}

        return $board_cells;;
	}
	
	public function printPuzzle()
	{
		print("\n");
		for($row = 0; $row < 9; $row++)
		{
			$end_value = ($row+1) *9;
			$count = 0;
			for($column = $row *9;$column < $end_value;$column++)
			{
				if($this->grid[$column]->getValue() != 0)
				{
					print($this->grid[$column]->getValue()." ");
				}
				else
				{
					print("_ ");
				}
				if($count == 2 || $count == 5)
				{
					print("| ");
				}
				$count++;
			}
			print("\n");
			if((($row+1) % 3) == 0 && $row != 8)
			{
				print("----------------------\n");
			}
		}
	}
	
//is functions
	
	public function isCellValueSet($cell, $section_number)
	{
		if($this->grid[$cell]->getValue() != 0)
		{
			return true;
		}
		else
		{
			if(count($this->grid[$cell]->getPossibilities()) == 1)
			{
				$pos_array = $this->grid[$cell]->getPossibilities();
				$value = $pos_array[0];
				$this->setCellValue($cell, $section_number, $value);
				//var_dump("set cell $cell to: ".$value);
				return true;	
			}
			else
			{
				return false;
			}
		}
	}
	
	public function isComplete()
	{
		$complete = TRUE;
		foreach($this->sections as $section)
		{
			if(!$section->isComplete())
			{
				$complete = FALSE;
			}
		}
		return $complete;
	}
	
//Getters	
	
	public function getSections()
	{
		return $this->sections;
	}
	
	public function getPuzzle()
	{
		return $this->grid;
	}
	
	/**
	 * This function will take the section number and determine what needs it needs to return in order
	 * to get the actual row number a cell is in.
	 * if section is 0, 1, or 2 all it needs to do is remove 1 from the section row to get the actual row number.
	 * if section is 3, 4, or 5 all it needs to do is add 2 to the section row to get the actual row number.
	 * if section is 6, 7, or 8 all it needs to do is add 5 to the section row to get the actual row number.
	 * @param object $section_number
	 * @return 
	 */
	public function getSectionRowMultiplier($section_number)
	{
		if($section_number < 3)
		{
			return Puzzle::TOP_SECTIONS_MULITIPLIER;;
		}
		elseif($section_number < 6)
		{
			return Puzzle::MIDDLE_SECTIONS_MULITIPLIER;
		}
		else
		{
			return Puzzle::BOTTOM_SECTIONS_MULITIPLIER;
		}
	}
	
	/**
	 * This function will return a value that need to be added to the section column number to
	 * get the acutal column number in the grid.
	 * if section is 0, 3, or 6 all it needs to do is remove 1 from the section column to get the actual column.
	 * if section is 1, 4, or 7 all it needs to do is add 2 to the section column to get the actual column.
	 * if section is 2, 5, or 8 all it needs to do is add 5 to the section column to get the actual column.
	 * @param object $section_number
	 * @return 
	 */
	public function getSectionColumnMultiplier($section_number)
	{
		if($section_number == 0 || $section_number == 3 || $section_number == 6)
		{
			return Puzzle::TOP_SECTIONS_MULITIPLIER;
		}
		elseif($section_number == 1 || $section_number == 4 || $section_number == 7)
		{
			return Puzzle::MIDDLE_SECTIONS_MULITIPLIER;
		}
		else
		{
			return Puzzle::BOTTOM_SECTIONS_MULITIPLIER;
		}
	}
	
	/**
	 * This function will get the row number in the grid that the cell is in.
	 * @param object $cell_number		The cell to look for.
	 * @param object $section_number	The section the cell is located in the grid.
	 * @return 							The grid row number 0-8
	 */
	public function getRowNumber($cell_number, $section_number)
	{
		$count =1;
		$found = false;
		$result = 0;
		while(!$found && $count < 4)
		{
			if($section_number == 0 || $section_number == 1 ||$section_number == 2)
			{
				if($this->sections[$section_number]->cellInRow($cell_number, $count))
				{
					$result = $count - $this->getSectionRowMultiplier($section_number);
					$found = true;
				}
			}
			else
			{
				if($this->sections[$section_number]->cellInRow($cell_number, $count))
				{
					$result = $count + $this->getSectionRowMultiplier($section_number);
					$found = true;
				}
			}
			$count++;
		}
		return $result;
	}
	
	/**
	 * This function will get the column number in the grid that the cell is in.
	 * @param object $cell_number		The cell to look 4.
	 * @param object $section_number	The section the cell is in.
	 * @return 							The grid column number 0-8
	 */
	public function getColumnNumber($cell_number, $section_number)
	{
		$count =1;
		$found = false;
		$result = 0;
		while(!$found && $count < 4)
		{	
			if($section_number == 0 || $section_number == 3 ||$section_number == 6)
			{
				if($this->sections[$section_number]->cellInColumn($cell_number, $count))
				{
					$result = $count - $this->getSectionColumnMultiplier($section_number);
					$found = true; 
				}
			}
			else
			{
				if($this->sections[$section_number]->cellInColumn($cell_number, $count))
				{
					$result = $count + $this->getSectionColumnMultiplier($section_number); 
					$found = true;
				}
			}
			$count++;
		}
		return $result;
	}
	
	/**
	 * This will get the section row number that a cell is in.
	 * @param object $section_number	The section number
	 * @param object $row_number		The grid row number
	 * @return 							the section row number
	 */
	public function getSectionRowNumber($section_number, $row_number)
	{
		if($section_number < 3)
		{
			return ($row_number + $this->getSectionRowMultiplier($section_number));
		}
		else
		{
			return ($row_number - $this->getSectionRowMultiplier($section_number));
		}
	}
	
	/**
	 * This function will take a grid column number and determine hte sectionColumn number
	 * @param object $section_number		The section that a current cell is in.
	 * @param object $col_number			the grid column number.
	 * @return 								A section column number.
	 */
	public function getSectionColumnNumber($section_number, $col_number)
	{
		if($section_number == 0 ||$section_number == 3 ||$section_number == 6)
		{
			return ($this->getSectionColumnMultiplier($section_number) + $col_number);
		}
		else
		{
			return ($col_number - $this->getSectionColumnMultiplier($section_number));
		}
	}
	
	/**
	 * This function will get all the cells that are in the specified row.
	 * @param object $row_num			The grid row number
	 * @param object $section_number	The section number so that the systems gets all the cells in the row.
	 * @return 							array of all cells in the current row.
	 */
	public function getAllCellsInRow($row_num, $section_number)
	{
		$result_array = array();
		$tmp_array_1 = array();
		$tmp_array_2 = array();
		$tmp_array_3 = array();
		$tmp_merger_1 = array();
		$row = $this->getSectionRowNumber($section_number, $row_num);
		if($section_number == 0 || $section_number == 1 || $section_number == 2)
		{
			$tmp_array_1 = $this->sections[0]->getRowNumbers($row);
			$tmp_array_2 = $this->sections[1]->getRowNumbers($row);
			$tmp_array_3 = $this->sections[2]->getRowNumbers($row);
		}
		elseif($section_number == 3 || $section_number == 4 || $section_number == 5)
		{
			$tmp_array_1 = $this->sections[3]->getRowNumbers($row);
			$tmp_array_2 = $this->sections[4]->getRowNumbers($row);
			$tmp_array_3 = $this->sections[5]->getRowNumbers($row);
		}
		else
		{
			$tmp_array_1 = $this->sections[6]->getRowNumbers($row);
			$tmp_array_2 = $this->sections[7]->getRowNumbers($row);
			$tmp_array_3 = $this->sections[8]->getRowNumbers($row);
		}
		$tmp_merger_1 = array_merge($tmp_array_1, $tmp_array_2);
		$result_array = array_merge($tmp_merger_1, $tmp_array_3);
		return $result_array;
	}
	
	/**
	 * This function will get all the cells that are in the specified column.
	 * @param object $col_num			The grid column number
	 * @param object $section_number	The section number to help get the correct column in the section.
	 * @return 							an array of all cells in the current column.
	 */
	public function getAllCellsInColumn($col_num, $section_number)
	{
		$result_array = array();
		$tmp_array_1 = array();
		$tmp_array_2 = array();
		$tmp_array_3 = array();
		$tmp_merger_1 = array();
		$col = $this->getSectionColumnNumber($section_number, $col_num);
		if($section_number == 0 || $section_number == 3 || $section_number == 6)
		{
			$tmp_array_1 = $this->sections[0]->getColumnNumbers($col);
			$tmp_array_2 = $this->sections[3]->getColumnNumbers($col);
			$tmp_array_3 = $this->sections[6]->getColumnNumbers($col);
		}
		elseif($section_number == 1 || $section_number == 4 || $section_number == 7)
		{
			$tmp_array_1 = $this->sections[1]->getColumnNumbers($col);
			$tmp_array_2 = $this->sections[4]->getColumnNumbers($col);
			$tmp_array_3 = $this->sections[7]->getColumnNumbers($col);
		}
		else
		{
			$tmp_array_1 = $this->sections[2]->getColumnNumbers($col);
			$tmp_array_2 = $this->sections[5]->getColumnNumbers($col);
			$tmp_array_3 = $this->sections[8]->getColumnNumbers($col);
		}
		$tmp_merger_1 = array_merge($tmp_array_1, $tmp_array_2);
		$result_array = array_merge($tmp_merger_1, $tmp_array_3);
		return $result_array;
	}
	
//Setters

	public function setCellValue($cell_number, $section_number, $value)
	{
		$this->grid[$cell_number]->setValue($value);
		$this->sections[$section_number]->removeFromPossibilities($value);
	}
	
//Remove

	/**
	 * This will go through all the cells in the specified row except for the specified cell
	 * and remove the passed value from there list of possibilities.
	 * @param object $cell_number		The specified cell
	 * @param object $value				The passed value to remove from the cells possibilities array
	 * @param object $row_number		The row of cells to go through. 0-8
	 */
	public function removePossibileValueFromAllCellsInRow($cell_number, $value, $row_number)
	{
		$first_cell_number_in_row = $row_number * Game::NUMBER_OF_ROWS;
		$last_cell_number_in_row = ((Game::NUMBER_OF_CELLS / Game::NUMBER_OF_ROWS) -1) + $first_cell_number_in_row;
		
		for($count = $first_cell_number_in_row; $count <= $last_cell_number_in_row; $count++)
		{
			$this->grid[$count]->removeFromPossibilities($value);
		}
	}
	
	/**
	 * This will go through all the cells in the specified column except for the specified cell
	 * and remove the passed value from their list of possibilities.
	 * @param object $cell_number		The specified cell
	 * @param object $value				The passed value to remove from the cells possibilities array
	 * @param object $column_number		The column of cells to go through. 0-8
	 */
	public function removePossibileValueFromAllCellsInColumn($cell_number, $value, $column_number)
	{
		$first_cell_in_column = $column_number;
		$last_cell_in_column = ((Game::NUMBER_OF_ROWS -1) * Game::NUMBER_OF_ROWS) + $column_number;

		for($count = $first_cell_in_column; $count <= $last_cell_in_column; $count+=9)
		{
			$this->grid[$count]->removeFromPossibilities($value);
		}
	}
	
	/**
	 * This will go through all the cells in the specified row except for the cells in the specified section
	 * and remove the passed value from there list of possibilities
	 * @param object $value				The value to remove from the cells in the possibility array
	 * @param object $section_number	The section the excluded cells are in
	 * @param object $row_num			The row number based upon the grid value (0-8)
	 */
	public function removePossibileValueFromAllCellsInRowExceptForInSection($value, $section_number, $row_num)
	{
		$cells_in_row = $this->getAllCellsInRow($row_num, $section_number);
		$section_row_number = $this->getSectionRowNumber($section_number, $row_num);
		foreach($cells_in_row as $cell)
		{
			if($this->sections[$section_number]->cellInRow($cell, $section_row_number) == FALSE)
			{//Will only get here if the current cell number is not in the array of the row in the current section
				$this->grid[$cell]->removeFromPossibilities($value);
			}
		}
	}
	
	/**
	 * This will go through all the cells in the specified column except for the cells in the specified section
	 * and remove the passed value from there list of possibilities
	 * @param object $value				The value to remove from the cells in the possibility array
	 * @param object $section_number	The section the excluded cells are in
	 * @param object $col_num			the grid column number
	 */	
	public function removePossibileValueFromAllCellsInColumnExceptForInSection($value, $section_number, $col_num)
	{
		$cell_in_col = $this->getAllCellsInColumn($col_num, $section_number);
		$section_col_number = $this->getSectionColumnNumber($section_number, $col_num);
		foreach($cell_in_col as $cell)
		{
			if(!in_array($cell, $this->sections[$section_number]->getColumnNumbers($section_col_number)))
			{
				$this->grid[$cell]->removeFromPossibilities($value);
			}
		}
	}
	
//Solve Functions	
	
	/**
	 * This function will go through all the possibilities of the given cell, and look at all the other cells in
	 * the passed section to see if any of the values could be removed because that value is set for one of the 
	 * other cells in the section.
	 * @param object $cell_number		The cell to look through all the possibilities.
	 * @param object $section_number	The section where the given cell is located.
	 */
	public function searchSectionForValues($cell_number, $section_number)
	{
		$cell_possibilities = $this->grid[$cell_number]->getPossibilities();
		$possibilities_toRemove = array();
		foreach($cell_possibilities as $possibility)
		{
			if(!$this->sections[$section_number]->isPossible($possibility))
			{
				$possibilities_toRemove[] = $possibility;
			}
		}
		foreach($possibilities_toRemove as $pos)
		{
			$this->grid[$cell_number]->removeFromPossibilities($pos);
		}
	}
	
	/**
	 * This function will go through all the cells on a giving row and if any of them are set
	 * will remove their value from the specified cells possibilities list.
	 * @param object $cell_number
	 * @param object $row_number 		Row number based on the 0 scale.
	 */
	public function searchRowValues($cell_number, $row_number)
	{
		$first_cell_number_in_row = $row_number * Game::NUMBER_OF_ROWS;
		$last_cell_number_in_row = ((Game::NUMBER_OF_CELLS / Game::NUMBER_OF_ROWS) -1) + $first_cell_number_in_row;
		
		for($count = $first_cell_number_in_row; $count <= $last_cell_number_in_row; $count++)
		{
			if($count != $cell_number)
			{
				if($this->grid[$count]->getValue() != 0)
				{
					$this->grid[$cell_number]->removeFromPossibilities($this->grid[$count]->getValue());
				}
			}
		}
	}
	
	/**
	 * This function will search through all the cells of a given column, and if any of them have a value set
	 * it will remove that value from the possibilities list of the passed cell.
	 * @param object $cell_number
	 * @param object $column_number		Column number based on 0 scale
	 */
	public function searchColumnValues($cell_number, $column_number)
	{
		$first_cell_in_column = $column_number;
		$last_cell_in_column = ((Game::NUMBER_OF_ROWS -1) * Game::NUMBER_OF_ROWS) + $column_number;
		
		for($count = $first_cell_in_column; $count <= $last_cell_in_column; $count+=9)
		{
			if($count != $cell_number)
			{
				if($this->grid[$count]->getValue() != 0)
				{
					$this->grid[$cell_number]->removeFromPossibilities($this->grid[$count]->getValue());
				}
			}
		}
	}
	
	/**
	 * This function will go through all the cells in the current section and see if the current possibility can go 
	 * in any other cell in that section. If not then it will set the value of the cell to the possibility and remove
	 * the possibility from the sections possibilities array.
	 * 
	 * @param object $cell_number
	 * @param object $section_number
	 * @return 
	 */
	public function sectionElimination($cell_number, $section_number)
	{
		$cell_possibilities = $this->grid[$cell_number]->getPossibilities();
		foreach($cell_possibilities as $pos)
		{
			//This will look through the rows to see if the value can not go anywhere else.
			$found_row_1 = $this->sectionRowElimination($cell_number, $section_number, 1, $pos);
			$found_row_2 = $this->sectionRowElimination($cell_number, $section_number, 2, $pos);
			$found_row_3 = $this->sectionRowElimination($cell_number, $section_number, 3, $pos);
			
			$row_num = $this->getRowNumber($cell_number, $section_number); //a value 0-8
			$col_num = $this->getColumnNumber($cell_number, $section_number); //a value 0-8
			
			if($found_row_1 == FALSE && $found_row_2 == FALSE && $found_row_3 == FALSE)
			{
				$this->setCellValue($cell_number, $section_number, $pos);
				//For rows
				$this->removePossibileValueFromAllCellsInRow($cell_number, $pos, $row_num);
				//For Columns
				$this->removePossibileValueFromAllCellsInColumn($cell_number, $pos, $col_num);
			}
			else
			{
				$this->doubleRowEliminationInSection($cell_number, $section_number, $pos, $row_num, $found_row_1, $found_row_2, $found_row_3);
			}
			
			//Column
			$found_col_1 = $this->sectionColumnElimination($cell_number, $section_number, 1, $pos);
			$found_col_2 = $this->sectionColumnElimination($cell_number, $section_number, 2, $pos);
			$found_col_3 = $this->sectionColumnElimination($cell_number, $section_number, 3, $pos);
			
			$this->doubleColumnEliminationInSection($cell_number, $section_number, $pos, $col_num, $found_col_1, $found_col_2, $found_col_3);
			
		} //end foreach
	}
	
	/**
	 * This function will determine if the value can only go on one row of the section, if so they it will
	 * remove the value from the cells in the section on different rows.
	 * @param object $cell_number		The Cell number
	 * @param object $section_number	The section the cell number is located
	 * @param object $value				The value that will be removed.
	 * @param object $row_num			The grid row number the cell is in grid. 
	 * @param object $found_r1			boolean to determine if the value was found in that row.
	 * @param object $found_r2			boolean to determine if the value was found in that row.
	 * @param object $found_r3			boolean to determine if the value was found in that row.
	 */
	public function doubleRowEliminationInSection($cell_number, $section_number, $value, $row_num, $found_r1, $found_r2, $found_r3)
	{	
		if($found_r1 == TRUE && $found_r2 == FALSE && $found_r3 == FALSE 
					&& $this->sections[$section_number]->cellInRow($cell_number, 1))
					{
						$this->removePossibileValueFromAllCellsInRowExceptForInSection($value, $section_number, $row_num);
					}
		elseif($found_r1 == FALSE && $found_r2 == TRUE && $found_r3 == FALSE
					&& $this->sections[$section_number]->cellInRow($cell_number, 2))
					{
						$this->removePossibileValueFromAllCellsInRowExceptForInSection($value, $section_number, $row_num);
					}
		elseif($found_r1 == FALSE && $found_r2 == FALSE && $found_r3 == TRUE
					&& $this->sections[$section_number]->cellInRow($cell_number, 3))
					{
						$this->removePossibileValueFromAllCellsInRowExceptForInSection($value, $section_number, $row_num);
					}		
	}
	
	/**
	 * This function will determine if a value can only go in one specific column of the section, and if so it will
	 * remove the value from the rest of the cells in the grid column, except for the cells in the specified section.
	 * @param object $cell_number		the specified cell.
	 * @param object $section_number	the specified section where the specified cell is located.
	 * @param object $value				the possibile value to remove from the other cells
	 * @param object $col_num			the grid column number where the specified cell is located.
	 * @param object $found_c1			boolean to determine if the value was found in that column.
	 * @param object $found_c2			boolean to determine if the value was found in that column.
	 * @param object $found_c3			boolean to determine if the value was found in that column.
	 */
	public function doubleColumnEliminationInSection($cell_number, $section_number, $value, $col_num, $found_c1, $found_c2, $found_c3)
	{
		if($found_c1 == TRUE && $found_c2 == FALSE && $found_c3 == FALSE 
					&& $this->sections[$section_number]->cellInColumn($cell_number, 1))
					{
						$this->removePossibileValueFromAllCellsInColumnExceptForInSection($value, $section_number, $col_num);
					}
		elseif($found_c1 == FALSE && $found_c2 == TRUE && $found_c3 == FALSE 
					&& $this->sections[$section_number]->cellInColumn($cell_number, 2))
					{
						$this->removePossibileValueFromAllCellsInColumnExceptForInSection($value, $section_number, $col_num);
					}
		elseif($found_c1 == FALSE && $found_c2 == FALSE && $found_c3 == TRUE 
					&& $this->sections[$section_number]->cellInColumn($cell_number, 3))
					{
						$this->removePossibileValueFromAllCellsInColumnExceptForInSection($value, $section_number, $col_num);
					}											
	}
	
	/**
	 * This function will go through all the cell numbers in the specified sections specified row.
	 * if the passed value can go in any cell that is not the passed cell then it will return true,
	 * else it will return false.
	 * @param object $cell_number
	 * @param object $section_number
	 * @param object $section_row_number
	 * @param object $poss_value
	 * @return 
	 */
	public function sectionRowElimination($cell_number, $section_number, $section_row_number, $poss_value)
	{
		$section_row_cells 			= $this->sections[$section_number]->getRowNumbers($section_row_number);//An array of row numbers.
		$last_cell_number_in_row 	= $section_row_cells[count($section_row_cells) - 1];
		
		$found = FALSE;
		if(in_array($cell_number, $section_row_cells))
		{
			foreach($section_row_cells as $c_number)
			{
				if($cell_number != $c_number)
				{
					if($this->grid[$c_number]->isPossible($poss_value))
					{//Means that the value is possible
						$found = TRUE;
					}
				}
			}
		}
		else
		{
			foreach($section_row_cells as $c_number)
			{
					if($this->grid[$c_number]->isPossible($poss_value))
					{//Means that the value is possible

						$found = TRUE;
					}
			}
		}
		return $found; //by returing true it means that the value can go somewhere in the row.
	}
	
	/**
	 * This function will go through all the cell numbers in the specified sections specified col.
	 * if the passed value can go in any cell that is not the passed cell then it will return true,
	 * else it will return false.
	 * @param object $cell_number
	 * @param object $section_number
	 * @param object $section_column_number
	 * @param object $poss_value
	 * @return 
	 */	
	public function sectionColumnElimination($cell_number, $section_number, $section_column_number, $poss_value)
	{
		$column_values = $this->sections[$section_number]->getColumnNumbers($section_column_number);
		$found = FALSE;
		if(in_array($cell_number, $column_values))
		{
			foreach($column_values as $col_val)
			{
				if($cell_number != $col_val)
				{
					if($this->grid[$col_val]->isPossible($poss_value))
					{
						$found = TRUE;
					}
				}
			}
		}
		else
		{
			foreach($column_values as $col_val)
			{
				if($this->grid[$col_val]->isPossible($poss_value))
				{
					$found = TRUE;
				}
			}
		}
		return $found;
	}
}